package com.songyang.yougnsql.core.data;

import com.songyang.yougnsql.common.ErrorMsg;
import com.songyang.yougnsql.common.ExceptionHandler;
import com.songyang.yougnsql.common.YoungSQLException;
import com.songyang.yougnsql.core.cache.AbstractCache;
import com.songyang.yougnsql.core.data.dataItem.DataItem;
import com.songyang.yougnsql.core.data.dataItem.DataItemImpl;
import com.songyang.yougnsql.core.data.logger.Logger;
import com.songyang.yougnsql.core.data.page.Page;
import com.songyang.yougnsql.core.data.page.PageOne;
import com.songyang.yougnsql.core.data.page.PageX;
import com.songyang.yougnsql.core.data.pagecache.PageCache;
import com.songyang.yougnsql.core.data.pageindex.PageIndex;
import com.songyang.yougnsql.core.data.pageindex.PageInfo;
import com.songyang.yougnsql.core.data.recover.Recover;
import com.songyang.yougnsql.core.transaction.TransactionManager;
import com.songyang.yougnsql.core.utils.Address;

/**
 * @ClassName DataManagerImpl
 * @Description
 * @date 2022/12/12 17:17
 * @Author yanceysong
 * @Version 1.0
 */
public class DataManagerImpl extends AbstractCache<DataItem> implements DataManager {

    public final PageCache pageCache;
    private final TransactionManager transactionManager;
    private final Logger logger;
    private final PageIndex pageIndex;
    private final int MAX_SELECT_TIMES = 5;
    public Page pageOne;

    public DataManagerImpl(PageCache pageCache, Logger logger, TransactionManager transactionManager) {
        super(0);
        this.pageCache = pageCache;
        this.logger = logger;
        this.transactionManager = transactionManager;
        this.pageIndex = new PageIndex();
    }

    /**
     * DataManager向上层提供的读取数据的方法
     *
     * @param uid uid
     * @return dataItem
     * @throws Exception 异常
     */
    @Override
    public DataItem read(long uid) throws Exception {
        DataItemImpl dataItem = (DataItemImpl) get(uid);
        //非法直接返回null
        if (!dataItem.isValid()) {
            dataItem.release();
            return null;
        }
        return dataItem;
    }

    /**
     * DataManager向上层提供插入数据的方法
     *
     * @param xid  事务的id
     * @param data 要插入的数据
     * @return uid 资源所在的地址
     * @throws Exception 异常
     */
    @Override
    public long insert(long xid, byte[] data) throws Exception {
        byte[] dataItemRaw = DataItem.wrapDataItemRaw(data);
        if (dataItemRaw.length > PageX.MAX_FREE_SPACE) throw new YoungSQLException(ErrorMsg.DATA_TOO_LARGE_EXCEPTION);
        PageInfo pageInfo = null;
        //选择对应的页插入数据，之所以要尝试多次，是因为可能刚添加一个页面就被其他线程拿走了
        for (int i = 0; i < MAX_SELECT_TIMES; i++) {
            pageInfo = pageIndex.select(dataItemRaw.length);
            if (pageInfo != null) {
                break;
            } else {
                //没有找到就插入一个新的页面
                int newPageNumber = pageCache.newPage(PageX.initRaw());
                //空的页面
                pageIndex.add(newPageNumber, PageX.MAX_FREE_SPACE);
            }
        }
        if (pageInfo == null) throw new YoungSQLException(ErrorMsg.DATABASE_BUSY_EXCEPTION);
        Page page = null;
        int freeSpace = 0;
        try {
            //查到页面
            page = pageCache.getPage(pageInfo.getPageNumber());
            //写插入日志
            byte[] log = Recover.getInsertLogBytes(xid, page, dataItemRaw);
            logger.insertLog(log);
            //写数据
            short offset = PageX.insert(page, dataItemRaw);
            //释放缓存
            page.release();
            return Address.addressToUid(page.getPageNumber(), offset);
        } finally {
            // 将取出的pg重新插入pIndex
            if (page != null) {
                pageIndex.add(pageInfo.getPageNumber(), PageX.getFreeSpace(page));
            } else {
                pageIndex.add(pageInfo.getPageNumber(), freeSpace);
            }
        }
    }

    @Override
    public void close() {
        super.close();
        logger.close();
        PageOne.setVcClose(pageOne);
        pageOne.release();
        pageCache.close();
    }

    // 为xid生成update日志
    public void logDataItem(long xid, DataItem di) {
        byte[] log = Recover.updateLog(xid, di);
        logger.insertLog(log);
    }

    @Override
    protected DataItem getForCache(long uid) throws Exception {
        Page page = pageCache.getPage(Address.getPageNumberByUid(uid));
        return DataItem.parseDataItem(page, Address.getOffsetByUid(uid), this);
    }

    @Override
    protected void releaseForCache(DataItem dataItem) {
        dataItem.page().release();
    }

    /**
     * 初始化第一个页面
     */
    public void initPageOne() {
        int pageNumber = pageCache.newPage(PageOne.initRaw());
        try {
            if (pageNumber != PageOne.PAGE_ONE_NUMBER) throw new YoungSQLException(ErrorMsg.UNKNOWN_ERROR);
            //获取第一页并且赋值
            pageOne = pageCache.getPage(pageNumber);
            //写入到磁盘
            pageCache.flushPage(pageOne);
        } catch (Exception e) {
            ExceptionHandler.handler(e);
        }
    }

    /**
     * 加载并且验证第一个页面判断数据库是否是正常关闭
     *
     * @return 验证结果
     */
    public boolean loadAndCheckPageOne() {
        try {
            Page pageOne = pageCache.getPage(PageOne.PAGE_ONE_NUMBER);
            //获取第一页并且校验
            return PageOne.checkVc(pageOne);
        } catch (Exception e) {
            ExceptionHandler.handler(e);
        }
        return false;
    }

    /**
     * 加载所有的页面，并且填充pageIndex
     */
    public void fillPageIndex() {
        int maxPageNumber = pageCache.getPageNumber();
        //从2开始算，第一页不是数据页
        for (int i = PageOne.PAGE_ONE_NUMBER + 1; i <= maxPageNumber; i++) {
            Page page = null;
            try {
                page = pageCache.getPage(i);
                pageIndex.add(page.getPageNumber(), PageX.getFreeSpace(page));
                page.release();
            } catch (Exception e) {
                ExceptionHandler.handler(e);
            } finally {
                assert page != null;
                page.release();
            }
        }
    }

    /**
     * 加载并检查第一个
     *
     * @return 是否需要日志恢复
     */
    boolean loadCheckPageOne() {
        try {
            pageOne = pageCache.getPage(1);
        } catch (Exception e) {
            ExceptionHandler.handler(e);
        }
        return PageOne.checkVc(pageOne);
    }

    /**
     * 释放一条dataItem
     *
     * @param dataItem 数据
     */
    public void releaseDataItem(DataItemImpl dataItem) {
        super.release(dataItem.getUid());
    }
}
